/** This class represents an instant messenger client 
 *  @author Copyright (c) 2004-2008 by chrmac@gmx.de */

package com.jtalker.client;

import java.awt.Color;
import java.awt.SystemColor;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.swing.JOptionPane;
import javax.swing.ListModel;
import javax.swing.UIManager;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;

import com.jtalker.client.JTalkerParser.ClientSettings;

/**
 * JTalker Client.
 * 
 * @author ChrM
 */
@SuppressWarnings("rawtypes")
public class JTalkerClient implements Comparator {
	final public static String RELEASE = "0.7.1";

	/** Constructor */
	public JTalkerClient() {
		this.configFile = System.getProperty("user.home") + File.separator + ".jtalker.conf";
		this.pluginDir = System.getProperty("user.home") + File.separator;
		this.isConnected = false;
		// System.out.println("-[1]-" + this.configFile + "-" + this.pluginDir);
		if (!loadConfigFile()) {
			// Fall back
			JOptionPane.showMessageDialog(null,
					"You are using JTalker for the first time.\nPlease check the configuration!",
					"Config file not found (" + this.configFile + ") !", JOptionPane.INFORMATION_MESSAGE);

			this.portNum = 4444;
			this.serverName = "localhost";
			this.useSSL = false;
			this.userName = System.getProperty("user.name");
			this.userStatus = "normal";
			this.userPasswd = "";
			this.savePassword = false;
			this.defaultBrowser = "firefox";
			this.defaultMailer = "kmail";
			this.guiPlaf = UIManager.getLookAndFeel().getClass().getName();
			this.guiTextColor = JTalkerClient.getColorAsString(SystemColor.textText);
			this.guiBgColor = JTalkerClient.getColorAsString(SystemColor.controlHighlight);
			this.newMailNotificationType = 2; // 0:none 1:toFront 2:notificationWindow 3:penetrant beep
			this.pluginDir = System.getProperty("user.home") + File.separator;
			this.pluginShortcuts = "";
		}

		this.myProtocol = new JTalkerClientProtocol(this);
	}

	/** Load the config file */
	private boolean loadConfigFile() {
		try {
			/*
			 * File myConfig = new File(this.configFile); FileReader fr = new FileReader(myConfig);
			 * java.io.BufferedReader in = new java.io.BufferedReader(fr); this.serverName = in.readLine(); this.portNum
			 * = Integer.parseInt(in.readLine()); this.defaultBrowser = in.readLine(); this.defaultMailer =
			 * in.readLine(); this.userName = in.readLine(); String passwd = in.readLine(); if(passwd != null)
			 * this.userPasswd = passwd; in.close(); fr.close();
			 */

			File myConfig = new File(this.configFile);
			JTalkerParser parser = new JTalkerParser();
			if (parser.parsing(myConfig)) {
				ClientSettings setttings = parser.getClientSettings();
				this.serverName = setttings.server_ip;
				this.portNum = Integer.parseInt(setttings.server_port);
				this.useSSL = Boolean.valueOf(setttings.server_ssl).booleanValue();
				this.defaultBrowser = setttings.apps_browser;
				this.defaultMailer = setttings.apps_mail;
				this.userName = setttings.account_name;
				this.userPasswd = setttings.account_passwd;
				if (this.userPasswd == null) {
					this.savePassword = false;
				}
				this.guiPlaf = setttings.look_plaf;
				if (this.guiPlaf == null) {
					this.guiPlaf = UIManager.getLookAndFeel().getClass().getName();
				}
				this.guiTextColor = setttings.look_textcolor;
				if (this.guiTextColor == null) {
					this.guiTextColor = JTalkerClient.getColorAsString(SystemColor.textText);
				}
				this.guiBgColor = setttings.look_bgcolor;
				if (this.guiBgColor == null) {
					this.guiBgColor = JTalkerClient.getColorAsString(SystemColor.controlHighlight);
				}
				try {
					this.newMailNotificationType = Integer.parseInt(setttings.notif_userlist);
				} catch (Exception e) {
					this.newMailNotificationType = 2;
				}
				this.pluginDir = setttings.plugins_dir;
				this.pluginShortcuts = setttings.plugins_shortcuts;
			} else {
				return false;
			}
		} catch (Exception _e) {
			System.err.println("*** ERROR: " + _e.toString());
			return false;
		}

		return true;
	}

	/**
	 * Helper method for converting an Color-object to a string like "#rrggbb"
	 * 
	 * @param color
	 *            The Color object that shoud be converted
	 * @return A String like "#rrggbb" or null.
	 */
	public static String getColorAsString(Color color) {
		if (color != null) {
			String retVal = "#";
			String hexNum = "";
			hexNum = Integer.toHexString(color.getRed());
			if (hexNum.length() == 1)
				hexNum = ("0" + hexNum);
			retVal += hexNum;
			hexNum = Integer.toHexString(color.getGreen());
			if (hexNum.length() == 1)
				hexNum = ("0" + hexNum);
			retVal += hexNum;
			hexNum = Integer.toHexString(color.getBlue());
			if (hexNum.length() == 1)
				hexNum = ("0" + hexNum);
			retVal += hexNum;
			return retVal;
		}

		return null;
	}

	/** Writes out the settings */
	public boolean saveSettings() {
		try {
			File myConfig = new File(this.configFile);
			FileWriter fw = new FileWriter(myConfig);

			String tmpConfig = "<" + JTalkerParser.CONFIG_ROOT + ">" + "\n  <" + JTalkerParser.CONFIG_SERVER + " "
					+ JTalkerParser.CONFIG_SERVER_IP + "=\"" + this.serverName + "\"" + " "
					+ JTalkerParser.CONFIG_SERVER_SSL + "=\"" + this.useSSL + "\"" + " "
					+ JTalkerParser.CONFIG_SERVER_PORT + "=\"" + this.portNum + "\" />" + "\n  <"
					+ JTalkerParser.CONFIG_ACCOUNT + " " + JTalkerParser.CONFIG_ACCOUNT_NAME + "=\"" + this.userName
					+ "\"";
			if (this.savePassword){
				tmpConfig += " " + JTalkerParser.CONFIG_ACCOUNT_PASSWD + "=\"" + this.userPasswd + "\" />";
			}else{
				tmpConfig += " />";
			}
			tmpConfig += ("\n  <" + JTalkerParser.CONFIG_APPS + " " + JTalkerParser.CONFIG_APPS_BROWSER + "=\""
					+ this.defaultBrowser + "\"" + " " + JTalkerParser.CONFIG_APPS_MAIL + "=\"" + this.defaultMailer
					+ "\" />" + "\n  <" + JTalkerParser.CONFIG_LOOK + " " + JTalkerParser.CONFIG_LOOK_PLAF + "=\""
					+ this.guiPlaf + "\"" + " " + JTalkerParser.CONFIG_LOOK_TEXTCOLOR + "=\"" + this.guiTextColor
					+ "\"" + " " + JTalkerParser.CONFIG_LOOK_BGCOLOR + "=\"" + this.guiBgColor + "\" />" + "\n  <"
					+ JTalkerParser.CONFIG_NOTIFICATION + " " + JTalkerParser.CONFIG_NOTIFICATION_USERLIST + "=\""
					+ this.newMailNotificationType + "\" />" + "\n  <" + JTalkerParser.CONFIG_PLUGINS + " "
					+ JTalkerParser.CONFIG_PLUGINS_DIR + "=\"" + this.pluginDir + "\"" + " "
					+ JTalkerParser.CONFIG_PLUGINS_SHORTCUT + "=\"" + this.pluginShortcuts + "\" />" + "\n</"
					+ JTalkerParser.CONFIG_ROOT + ">");
			fw.write(tmpConfig);
			fw.close();
			return true;
		} catch (Exception _e) {
			System.err.println("*** ERROR: " + _e.toString());
			return false;
		}
	}

	/** Sets servers properties (Port and Adress) */
	public void setServerProps(int _portNum, String _serverName) {
		this.portNum = _portNum;
		this.serverName = _serverName;
	}

	/** Gets server data */
	public String getServerInfo() {
		return this.serverName + ":" + this.portNum;
	}

	/** Gets a member by short name */
	public MemberData getMemberByName(String name) {
		for (int i = 0; i < this.allMembers.size(); i++) {
			if (this.allMembers.get(i).name.equals(name)) {
				// Found User
				return this.allMembers.get(i);
			}
		}

		return null;
	}

	/** Adds a new member or a new message to the List */
	@SuppressWarnings("unchecked")
	public boolean addMember(String name, String status, String userType, String message, String ip, String subject,
			String type) {

		for (int i = 0; i < this.allMembers.size(); i++) {
			MemberData data = this.allMembers.get(i);
			if (data.short_name.equals(name)) {
				// Found User -> add new Message
				if (message != null) {
					data.addMessage(message, subject, type);
				}
				if (status != null) {
					data.status = status;
				}
				return true;
				// TODO: Check if the ip has changed!
			}
		}

		// New User -> add to Member List
		MemberData newMember = new MemberData(name, ip, status, userType);
		if (message != null) {
			newMember.addMessage(message, subject, type);
		}
		this.allMembers.add(newMember);

		// Sort List
		Collections.sort((List) this.allMembers, (Comparator) this);

		return true;
	}

	// /////////////// DEBUG MAIN LOOP //////////////////////////////
	public void mainLoop() {
		if (!this.connect())
			return;
		this.loginOnServer();
		// this.createNewAccount("NewUser", "myPass");
		this.sendMessage("Hallo Du!", "MrXX");
		this.lookUp();
		this.closeConnection();
	}

	// //////////////////////////////////////////////////////////////

	/** Starts the connection */
	@SuppressWarnings("unused")
	public boolean connect() {
		System.out.println(">>> Open TCP/IP connection to: " + this.serverName + ":" + this.portNum);
		try {
			if (this.useSSL) {
				// SSL
				/*
				 * this.kkSocket = SSLSocketFactory.getDefault().createSocket(this.serverName, this.portNum);
				 */

				// TODO: ignore all SSL-actions (Cert verification) or not
				this.trustAllCerts();

				System.out.println("+ Try to create a SSL-Socket...");
				SSLSocketFactory factory = HttpsURLConnection.getDefaultSSLSocketFactory();
				this.kkSocket = (SSLSocket) factory.createSocket(this.serverName, this.portNum);

				// Connect to the server
				System.out.println("+ Start SSL handshake...");
				((SSLSocket) this.kkSocket).startHandshake();

				// Retrieve the server's certificate chain
				java.security.cert.Certificate[] serverCerts = ((SSLSocket) this.kkSocket).getSession()
						.getPeerCertificates();
			} else {
				// NO SSL
				System.out.println("+ Try to create a Socket...");
				this.kkSocket = new Socket(this.serverName, this.portNum);
			}
			// Set Timeout to 8 sec.
			this.kkSocket.setSoTimeout(10000);
			this.out = new PrintWriter(this.kkSocket.getOutputStream(), true);
			this.in = new BufferedReader(new InputStreamReader(this.kkSocket.getInputStream()));
		} catch (UnknownHostException e) {
			System.err.println("*** ERROR: Unknown host: " + this.serverName);
			this.showErrorMessage(e.toString());
			return false;
		} catch (IOException e) {
			System.err.println("*** ERROR: Couldn't get I/O for the connection to: " + this.serverName + " ("
					+ e.toString() + ")");
			this.showErrorMessage(e.toString());
			return false;
			/*
			 * } catch (java.security.cert.CertificateEncodingException _e) {
			 * System.err.println("*** ERROR: Wrong Certificate: " + _e.toString());
			 * this.showErrorMessage(_e.toString()); return false;
			 */
		} catch (Exception e) {
			System.err.println("*** ERROR: " + e.toString());
			this.showErrorMessage(e.toString());
			return false;
		}

		String fromServer; // For servers reply
		// Get servers first answer after connect
		try {
			fromServer = this.in.readLine();
			System.out.println("[recv]: " + fromServer);

			if (!this.saveServersReply(fromServer)) {
				return false;
			} else {
				// Init server listener
				// set timeout to infinite!
				this.kkSocket.setSoTimeout(0);
				this.clientThread = new JTalkerClientThread(this, this.in);
				this.clientThread.start();
			}

		} catch (Exception e) {
			System.err.println("*** Can't read servers reply! " + e.toString());
			this.showErrorMessage(e.toString());
			return false;
		}

		this.isConnected = true;
		return true;
	}

	/** Occures, if the main loop thread dies */
	public void mainLoopError() {
		if (this.isConnected) {
			JOptionPane.showMessageDialog(null, "Server connection error! Connect again.", "Big problem",
					JOptionPane.ERROR_MESSAGE);
			this.closeConnection();
		}
	}

	/** Shows a error box */
	private void showErrorMessage(String _text) {
		JOptionPane.showMessageDialog(null, "Connection Error:\n" + _text, "Connection Problems",
				JOptionPane.ERROR_MESSAGE);
	}

	// ----------------------- Main Interfaces --------------------------------
	/** Send a message */
	public boolean sendMessage(String message, String receiver) {
		System.out.println(">>> Send Message...");
		return this.myProtocol.sendMessage(message, receiver, "normal", null, null);
	}

	/** Send a message of type "_type" */
	public boolean sendMessage(String message, String receiver, String type, String subject) {
		System.out.println(">>> Send Message... Type:" + type);
		return this.myProtocol.sendMessage(message, receiver, type, subject, null);
	}

	/** Send a message of type "type" and "id" */
	public boolean sendMessage(String message, String receiver, String type, String subject, String id) {
		System.out.println(">>> Send Message... Type:" + type + " ID:" + id);
		return this.myProtocol.sendMessage(message, receiver, type, subject, id);
	}

	/** Looks up to server for new messages or current user List */
	public boolean lookUp() {
		System.out.println(">>> Lookup for Messages...");
		return this.myProtocol.sendUserStatus(false);
	}

	/** Logins user */
	public boolean loginOnServer() {
		this.STATE = STATE_LOGGING;
		System.out.println(">>> Login on server...");
		return this.myProtocol.sendUserStatus(true);
	}

	/** Creates a new Account on the server */
	public boolean createNewAccount(String userName, String password) {
		boolean retVal = false;
		System.out.println(">>> Try to create a new account...");

		// Close Connection first - if connected
		if (this.isConnected)
			this.closeConnection();

		if (this.connect()) {
			try {
				this.STATE = STATE_ACCOUNT_CREATION;
				retVal = this.myProtocol.sendNewAccount(userName, password);

				// TODO: replacement it using MESSAGE type="register"
				return retVal;

			} catch (Exception _e) {
				System.err.println("*** ERROR: Can't create accout: " + _e.toString());
				return false;
			}
		} else {
			return false;
		}
	}

	/** Closes the connection */
	public void closeConnection() {
		System.out.println(">>> Close connection");
		this.STATE = STATE_OFFLINE;
		this.clientThread.terminate();
		this.removeAllConnectionObjects();
		this.allMembers.clear();
		this.memberList.propertyChanged();

		try {
			this.out.close();
			this.in.close();
			this.isConnected = false;
			this.kkSocket.close();

			// Inform all threads about disconnect
			this.setLastCommandResponse("disconnect01", null, null);
		} catch (Exception e) {
			System.err.println("*** Can't close this connection: " + e.toString());
		}
	}

	// -------------------------------------------------------------------------

	/** Sends a stream to the servers */
	public boolean sendToServer(String stream) {
		// send XML stream
		try {
			if (stream != null) {
				System.out.println("[send]: " + stream);
				this.out.println(stream);
				return true;
			} else {
				return false;
			}
		} catch (Exception e) {
			return false;
		}

		// get answer
		/*
		 * try { //String fromServer = this.in.readLine(); String fromServer = this.jct.getLastPackage();
		 * 
		 * if(fromServer == null) return null;
		 * 
		 * System.out.println("[recv]: " + fromServer);
		 * 
		 * JTalkerParser myParser = new JTalkerParser(); if(myParser.parsing(fromServer)) return myParser; else return
		 * null;
		 * 
		 * } catch (Exception _e) { System.err.println("*** Exception while reading from socket: " + _e.getMessage());
		 * return null; }
		 */
	}

	/** Parse and save servers reply */
	private boolean saveServersReply(String replyMsgServers) {
		try {
			this.myProtocol.processServerPackage(replyMsgServers);

			/*
			 * JTalkerParser tmpParser = new JTalkerParser(); if(!tmpParser.parsing(_serversReply)) return false;
			 * 
			 * for( int i = 0; i < tmpParser.getMemberNum(); i++ ) { this.addMember(tmpParser.getAMember(i).name,
			 * tmpParser.getAMember(i).status, tmpParser.getAMember(i).type, null, tmpParser.getAMember(i).ip, null,
			 * null); } // save messages for( int i = 0; i < tmpParser.getMessagesNum(); i++ ) {
			 * this.addMember(tmpParser.getAMessage(i).sender, tmpParser.getAMessage(i).senderStatus, null,
			 * tmpParser.getAMessage(i).content, null, tmpParser.getAMessage(i).messageSubject,
			 * tmpParser.getAMessage(i).messageType); }
			 * 
			 * this.cleanUpList(tmpParser);
			 */

		} catch (Exception e) {
			System.err.println("*** Can't save servers reply! " + e.toString());
			return false;
		}

		return true;
	}

	/** Removes all connections on disconnect and closes all opened chat windows */
	public void removeAllConnectionObjects() {
		for (int i = 0; i < this.allMembers.size(); i++) {
			this.allMembers.get(i).destroyChatWindow();
		}

	}

	/** Clean ups list */
	synchronized public void cleanUpList(JTalkerParser tempParser) {
		// check if some members are going off-line
		// TODO: Use IDs to check changes!!!
		// if(_tmpParser.getMemberNum() != this.allMembers.size()) {
		// if (true) {
		// System.out.println("+ Num of members changed - update list! " +
		// _tmpParser.getMemberNum() + "/" + this.allMembers.size());
		// get local members
		boolean userConnected = false;
		for (int i = 0; i < this.allMembers.size(); i++) {
			userConnected = false;
			MemberData tempMember = this.allMembers.get(i);
			// get servers members
			for (int j = 0; j < tempParser.getMemberNum(); j++) {
				if (tempMember.short_name.equals(tempParser.getAMember(j).name)) {
					if (!tempMember.isConnected()) {
						tempMember.setConnected();
					}
					userConnected = true;
				}
			}

			// set this user as disconnected and remove local member of no unread messages inside
			if (!userConnected) {
				if (tempMember.isConnected()) {
					tempMember.setDisconnected();
				}
				if (!tempMember.hasNewMessage && !tempMember.isChatWindowOpen()) {
					// Remove user finally - only if no messages and no open chats
					this.allMembers.remove(i);
				}
			}
		}
		// }
	}

	/** Trust all SSL certifications */
	private boolean trustAllCerts() {
		// Create a trust manager that does not validate certificate chains
		TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
			public java.security.cert.X509Certificate[] getAcceptedIssuers() {
				return null;
			}

			public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {
			}

			public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {
			}
		} };

		// Install the all-trusting trust manager
		try {
			SSLContext sc = SSLContext.getInstance("SSL");
			sc.init(null, trustAllCerts, new java.security.SecureRandom());
			HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
		} catch (Exception e) {
			return false;
		}
		return true;
	}

	/** Gets the last command response */
	// public String getLastCommandResponse() {
	/*
	 * try { int timeout = 50; while(this.lastCommandResponse == null && timeout > 0) { Thread.sleep(50); timeout--; } }
	 * catch (Exception _e) { System.err.println("*** Can't get command response: " + _e.toString()); }
	 */

	// return this.lastCommandResponse;
	// }

	/** Sets the last command response */
	public void setLastCommandResponse(String id, String from, String content) {
		this.lastCommandResponse = content; // Only for backward compatibility.

		// Dispatch the response to all registered objects
		for (int i = 0; i < this.allCommandResponseObjects.size(); i++) {
			this.allCommandResponseObjects.get(i).setCommandAnswer(id, from, content);
		}
	}

	/** Register an object for receiving command responses */
	public void registerForCommandResponse(JTalkerClientCommandInterface command) {
		for (int i = 0; i < this.allCommandResponseObjects.size(); i++) {
			if (this.allCommandResponseObjects.get(i) == command) {
				System.out.println("*** registerForCommandResponse: object already registered: " + command.toString());
				return;
			}
		}
		this.allCommandResponseObjects.add(command);
	}

	/** Deregister an object from command responses */
	public void deRegisterForCommandResponse(JTalkerClientCommandInterface _object) {
		System.out.println("*** deRegisterForCommandResponse: removing object: " + _object.getClass().getName());
		if (this.allCommandResponseObjects.remove(_object))
			System.out.println("*** deRegisterForCommandResponse: object removed: " + _object.getClass().getName());
	}

	// Some Plugin shortcut methods
	// All this is frontend stuff and should be moved
	// to the frontend classes later
	/** Add a plugin to the shortcut list */
	public boolean addPluginShortcut(String name) {
		if (!this.isPluginShortcut(name)) {
			this.pluginShortcuts += (name + ",");
			return true;
		}

		return false;
	}

	/** Removes a plugin shortcut */
	public boolean removePluginShortcut(String name) {
		String[] allEmbPlugins = this.pluginShortcuts.split(",");
		this.pluginShortcuts = "";
		boolean retVal = false;

		for (int i = 0; i < allEmbPlugins.length; i++) {
			if (!(allEmbPlugins[i].trim()).equals(name)) {
				this.addPluginShortcut(allEmbPlugins[i].trim());
				retVal = false;
			}
		}

		return retVal;
	}

	/** Checks if a plug-in shortcut exists for the given name */
	public boolean isPluginShortcut(String name) {
		String[] allEmbPlugins = this.pluginShortcuts.split(",");
		for (int i = 0; i < allEmbPlugins.length; i++) {
			if (allEmbPlugins[i].trim().equals(name)){
				return true;
			}
		}
		return false;
	}

	/** Gets an plugin shortcut array */
	public String[] getPluginShortcuts() {
		String[] allEmbPlugins = this.pluginShortcuts.split(",");
		return allEmbPlugins;
	}

	/** Gets the frontend */
	public JTalkerClientFront getFrontend() {
		return this.myFrontend;
	}

	/** Sets the frontend */
	public void setFrontend(JTalkerClientFront jTalkerClientFront) {
		if (this.myFrontend == null) {
			this.myFrontend = jTalkerClientFront;
		}
	}

	// States
	public static final int STATE_OFFLINE = 0;
	public static final int STATE_LOGGING = 1;
	public static final int STATE_CONNECTED = 2;
	public static final int STATE_ACCOUNT_CREATION = 3;
	public int STATE = STATE_OFFLINE;

	private Socket kkSocket = null;
	private PrintWriter out = null;
	private BufferedReader in = null;
	public JTalkerClientProtocol myProtocol = null;
	private JTalkerClientThread clientThread = null;

	// Members for the Frontend
	private JTalkerClientFront myFrontend = null;
	public boolean isConnected = false;
	public int portNum = 4444;
	public String serverName = "localhost";
	public boolean useSSL = false;
	public String userName;
	public String userStatus;
	public String userPasswd;
	public boolean savePassword;
	public String defaultBrowser;
	public String defaultMailer;
	public String guiPlaf;
	public String guiTextColor;
	public String guiBgColor;
	/** 0: none 1:toFront 2:notification window 3:penetrant beep */
	public int newMailNotificationType = 2;
	public String configFile;
	public String pluginDir;
	/** ","-separated list of plugins, which should be embedded in the main window */
	private String pluginShortcuts;
	/** A List of MemberData Objects */
	public ArrayList<MemberData> allMembers = new ArrayList<MemberData>();
	/** A List of MemberData Objects */
	public ArrayList<JTalkerClientCommandInterface> allCommandResponseObjects = new ArrayList<JTalkerClientCommandInterface>();
	public MemberList memberList = new MemberList();
	private String lastCommandResponse = null;

	/** static main-method */
	public static void main(String[] args) throws IOException {
		JTalkerClient theClient = new JTalkerClient();
		theClient.mainLoop();
	}

	// Comparator methods
	public int compare(Object _o1, Object _o2) {
		return -1 * ((MemberData) _o1).type.compareToIgnoreCase(((MemberData) _o2).type);
	}

	public boolean equals(Object _obj) {
		// Not needed yet
		return true;
	}

	/** Model for the JList with all users */
	public class MemberList implements ListModel {
		private ListDataListener myDataListener = null;

		public MemberList() {
		}

		// Notify a Change
		public void propertyChanged() {
			this.myDataListener.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, allMembers
					.size() - 1, 0));
		}

		// List Model Listener
		public void addListDataListener(ListDataListener l) {
			this.myDataListener = l;
		}

		public Object getElementAt(int index) {
			return allMembers.get(index);
		}

		public int getSize() {
			return allMembers.size();
		}

		public void removeListDataListener(ListDataListener l) {
		}
	}

	/** Inner class with informations about all users and theirs messages */
	public class MemberData {
		public MemberData(String name, String ip, String status, String type) {
			this.name = name + " AT " + ip; // Backward compatibility :-(
			this.status = status;
			// this.short_name = _name.substring(0, _name.lastIndexOf(" AT "));
			this.short_name = name;
			this.ip = ip;
			this.type = type;
			if (this.type == null) {
				this.type = JTalkerParser.USER_TYPE_PERSISTENT;
			}
		}

		public void clearMessages() {
			this.messageList.clear();
		}

		/** Sets User Status to Disconnected */
		public void setDisconnected() {
			this.is_connected = false;
			// Save last state of unread messages
			boolean tmpState = this.hasNewMessage;
			this.addMessage("<table border=\"0\" width=\"100%\"><tr><td bgcolor=\"#ff0000\">"
					+ MemberData.DISCONNECT_MESSAGE + "</td></tr></table>", "User is offline", "normal");

			// Don't notify this message if chat window is not open
			if (!this.isChatWindowOpen()) {
				this.hasNewMessage = tmpState;
			}
		}

		/** Sets User Status to Connected (again) */
		public void setConnected() {
			this.is_connected = true;
			// Save last state of unread messages
			boolean tmpState = this.hasNewMessage;
			this.addMessage("<table border=\"0\" width=\"100%\"><tr><td bgcolor=\"#00ff00\">"
					+ MemberData.CONNECT_MESSAGE + "</td></tr></table>", "User is online", "normal");

			// Don't notify this message if chat window is not open
			if (!this.isChatWindowOpen()) {
				this.hasNewMessage = tmpState;
			}
		}

		/** Gets User Connection-Status */
		public boolean isConnected() {
			return this.is_connected;
		}

		public void addMessage(String content, String subject, String type) {
			if (content == null) {
				return;
			}
			this.hasNewMessage = true;
			this.messageList.add(new Message(content, new DateMan().getTime(2), subject, type));
			// System.out.println("------content-----" + content);
			if (this.messageList.size() > 30) {
				this.messageList.remove(0);
			}
			// DEBUG
			// if(this.isChatWindowOpen())
			// this.chat_window.updateWindow();
		}

		/** Gets a message (text only messages) */
		public synchronized String getMessage(int index) {
			try {
				Message tmpMsg = this.messageList.get(index);
				if (tmpMsg.type.equals("normal")) {
					// this.hasNewMessage = false;
					return tmpMsg.content;
				} else {
					return null;
				}
			} catch (Exception e) {
				System.err.println("*** No Messages from this User: " + this.name);
				return null;
			}
		}

		/** Shows a save as Dialog for cached files */
		public synchronized boolean saveBASE64Files() {
			// Searching for files
			for (int i = 0; i < this.numOfMessages(); i++) {
				Message tmpMsg = this.messageList.get(i);
				if (tmpMsg.type.equals("file")) {
					// this.hasNewMessage = false;
					BASE64Handler b64h = new BASE64Handler(this.chat_window);
					if (!b64h.saveDecodedData(tmpMsg.content, tmpMsg.subject)){
						System.out.println("*** Can't decode file: " + b64h.getLastError());
					}
					this.messageList.remove(i);
					return true;
				}
			}
			// No Files found
			return false;
		}

		public String getDate(int index) {
			try {
				return this.messageList.get(index).date;
			} catch (Exception _e) {
				System.err.println("*** No Messages from this User: " + this.name);
				return null;
			}
		}

		/** Gets number of Messages */
		public int numOfMessages() {
			return this.messageList.size();
		}

		/** Opens the chat window for this user */
		public void showChatWindow(JTalkerClientFront parent) {
			if (this.chat_window == null) {
				this.chat_window = new JTalkerFrontChat(parent, this);
			}
			this.chat_window.setVisible(true);
			this.chat_window.toFront();
			this.chat_window.updateWindow();
		}

		/** Checks if the chat window is open */
		public boolean isChatWindowOpen() {
			if (this.chat_window == null) {
				return false;
			}
			return this.chat_window.isVisible();
		}

		/** Destroys the chat window */
		public void destroyChatWindow() {
			if (this.chat_window != null)
				this.chat_window.dispose();
		}

		// Private members
		private final static String DISCONNECT_MESSAGE = "Server: User is OFFLINE!";
		private final static String CONNECT_MESSAGE = "Server: User is ONLINE!";
		/** Member name */
		public String name;
		/** IP address */
		public String ip;
		/** userType */
		public String type;
		/** Member name without IP */
		public String short_name;
		/** Member status */
		public String status;
		/** Is there an unread message? */
		public boolean hasNewMessage = false;
		/** List of messages */
		private ArrayList<Message> messageList = new ArrayList<Message>();
		private boolean is_connected = true;
		// Only for a swing based client
		private JTalkerFrontChat chat_window = null;

		public class Message {
			public Message(String content, String date, String subject, String type) {
				this.content = content;
				this.date = date;
				this.subject = subject;
				this.type = type;
				if (type == null) {
					this.type = "normal";
				}
			}

			public String content;
			public String date;
			public String subject;
			public String type;
		}
	}
}
